// TODO: support closure (first-class function)
use crate::{error::TonglunError, pools::PoolRef, utils::TonglunBitcast};

/// A reference to a value, directly passed to other languages
/// When the data is smaller than 16 bytes, it is directly stored in the bytes field
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct ValueRef {
    pub tag: ValueTag,
    pub data: u64,
}

#[derive(Default, Debug, Clone, Copy)]
#[repr(C)]
pub enum ValueTag {
    #[default]
    None = 1,
    Bool = 2,
    Int64 = 3,
    Float64 = 4,
    Complex32 = 5,
    Complex64 = 6,
    String = 7,
    Array = 8,
    HList = 9,
}

/// Rust representation of a ValueRef, type-safe
pub enum RustValue {
    None,
    Bool(bool),
    Int64(i64),
    Float64(f64),
    Complex32(Complex32),
    Complex64(PoolRef),
    String(PoolRef),
    Array(PoolRef),
    HList(PoolRef),
}

#[derive(Default, Clone, Copy)]
#[repr(C)]
pub struct Complex32 {
    pub real: f32,
    pub imag: f32,
}

#[derive(Clone, Copy, PartialEq)]
#[repr(C)]
pub enum ArrayDType {
    Bool = 0,
    Int8 = 1,
    Int16 = 2,
    Int32 = 3,
    Int64 = 4,
    UInt8 = 5,
    UInt16 = 6,
    UInt32 = 7,
    UInt64 = 8,
    Float32 = 9,
    Float64 = 10,
    Complex32 = 11,
    Complex64 = 12,
}

impl ArrayDType {
    pub fn from_typecode(typecode: &[u8]) -> Result<Self, TonglunError> {
        let res = match typecode {
            b"b8" => Self::Bool,
            b"i8" => Self::Int8,
            b"i16" => Self::Int16,
            b"i32" => Self::Int32,
            b"i64" => Self::Int64,
            b"u8" => Self::UInt8,
            b"u16" => Self::UInt16,
            b"u32" => Self::UInt32,
            b"u64" => Self::UInt64,
            b"f32" => Self::Float32,
            b"f64" => Self::Float64,
            b"c32" => Self::Complex32,
            b"c64" => Self::Complex64,
            _ => {
                return Err(TonglunError::InvalidTypecode(
                    String::from_utf8_lossy(typecode).to_string(),
                ))
            }
        };
        Ok(res)
    }
}

impl RustValue {
    pub fn to_value_ref(&self) -> ValueRef {
        match self {
            RustValue::None => ValueRef {
                tag: ValueTag::None,
                data: 0,
            },
            RustValue::Bool(value) => ValueRef {
                tag: ValueTag::Bool,
                data: if *value { 1 } else { 0 },
            },
            RustValue::Int64(value) => ValueRef {
                tag: ValueTag::Int64,
                data: (*value)._tl_to_unsigned(),
            },
            RustValue::Float64(value) => ValueRef {
                tag: ValueTag::Float64,
                data: (*value)._tl_to_unsigned(),
            },
            RustValue::Complex64(pool_ref) => ValueRef {
                tag: ValueTag::Complex64,
                data: pool_ref.to_u64(),
            },
            RustValue::Complex32(pool_ref) => ValueRef {
                tag: ValueTag::Complex32,
                data: pool_ref._tl_to_unsigned(),
            },
            RustValue::String(pool_ref) => ValueRef {
                tag: ValueTag::String,
                data: pool_ref.to_u64(),
            },
            RustValue::Array(pool_ref) => ValueRef {
                tag: ValueTag::Array,
                data: pool_ref.to_u64(),
            },
            RustValue::HList(pool_ref) => ValueRef {
                tag: ValueTag::HList,
                data: pool_ref.to_u64(),
            },
        }
    }

    pub fn from_value_ref(value_ref: ValueRef) -> Self {
        match value_ref.tag {
            ValueTag::None => RustValue::None,
            ValueTag::Bool => RustValue::Bool(value_ref.data != 0),
            ValueTag::Int64 => RustValue::Int64(i64::_tl_from_unsigned(value_ref.data)),
            ValueTag::Float64 => RustValue::Float64(f64::_tl_from_unsigned(value_ref.data)),
            ValueTag::Complex32 => {
                RustValue::Complex32(Complex32::_tl_from_unsigned(value_ref.data))
            }
            ValueTag::Complex64 => RustValue::Complex64(PoolRef::from_u64(value_ref.data)),

            ValueTag::String => RustValue::String(PoolRef::from_u64(value_ref.data)),
            ValueTag::Array => RustValue::Array(PoolRef::from_u64(value_ref.data)),
            ValueTag::HList => RustValue::HList(PoolRef::from_u64(value_ref.data)),
        }
    }
}
